package org.javahispano.javacup.tacticas_aceptadas.diegopl;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.TreeSet;

import org.javahispano.javacup.modelo.*;

import java.util.Iterator;

public class TacticaPalako implements Tactica {
	
	private static final int NUM_DEFENSAS=6;

	//Estado en el se inicial la maquina de estados
	private static final int DESCONOCIDO = 1;
	
	//Uno de nuestros jugadores ha hecho un pase y otro ha de recogerlo
	private static final int PASANDO = 2;
	
	//Uno de los jugadores contrarios ha golpeado el balon
	private static final int INTERCEPTANDO = 3;
	
	//Posiblemente uno de los dos a golpeado el balon, pero no se sabe quien hasta esta iteracion
	private static final int PASANDO_O_INTERCEPTANDO = 4;
	
	private class EstadoBalon {
		public Posicion posicion;
		public double altura;
		
		public EstadoBalon(Posicion posicion, double altura) {
			this.posicion=posicion;
			this.altura=altura;
		}
	}
	
	private class Pase {
		public int indiceReceptor;
		public Posicion destino;
		public int duracion;
		public double ajustePotencia;

		public Pase(int indiceReceptor, Posicion destino, int duracion, double ajustePotencia) {
			this.indiceReceptor = indiceReceptor;
			this.destino = destino;
			this.duracion = duracion;
			this.ajustePotencia = ajustePotencia;
		}
	}
	
	/**
	 * Usada como comparador de como de buena es una posicion, se usa por ejemplo en la funcion de calculo de 
	 * pases para mantener una lista de posibles  puntos de pases ordenada de mejor a peor.
	 */
	private class ComparadorPosiciones implements Comparator<Posicion>{

		public int compare(Posicion p1, Posicion p2) {
			if (p1.distancia(Constantes.centroArcoSup) < p2.distancia(Constantes.centroArcoSup)) {
				return -1;
			} else if (p1.distancia(Constantes.centroArcoSup) == p2.distancia(Constantes.centroArcoSup)) {
				return 0;
			} else {
				return 1;
			}
		}
	}
	
	private SituacionPartido situacionPartido;
	private SituacionPartido ultimaSituacionPartido;
	private EstadoBalon ultimoEstadoBalon;
	
	private Pase paseEnCurso=null;
	
	private int estado = DESCONOCIDO;
	
	Random rand = new Random();
    LinkedList<Comando> comandos = new LinkedList<Comando>();

    TacticaDetalle detalle = new TacticaPalakoDetalle();
    
    Posicion alineacionAtaque[] = new Posicion[]{
    	new Posicion(0.0, -Constantes.LARGO_CAMPO_JUEGO/2),
        new Posicion(-10.0, -36.0),
        new Posicion(10.0,-36.0),
        new Posicion(-18.0,-18.0),
        new Posicion(18.0,-18.0),
        new Posicion(0.0,0.0),
        new Posicion(-24.0,10.0),
        new Posicion(24.0,10.0),
        new Posicion(-15,40.0),
        new Posicion(0.0,30.0),
        new Posicion(15,40.0)
    };

    Posicion alineacionDefensa[]=new Posicion[]{
    		new Posicion(0.0, -Constantes.LARGO_CAMPO_JUEGO/2),
            new Posicion(-10.223776223776223,-41.57239819004525),
            new Posicion(10.461538461538462,-41.80995475113122),
            new Posicion(-17.594405594405593,-29.457013574660635),
            new Posicion(17.832167832167833,-29.21945701357466),
            new Posicion(0.7132867132867133,-18.29185520361991),
            new Posicion(-14.027972027972028,-7.126696832579185),
            new Posicion(13.79020979020979,-7.364253393665159),
            new Posicion(-11.65034965034965,19.717194570135746),
            new Posicion(-0.951048951048951,0.7126696832579186),
            new Posicion(10.223776223776223,19.004524886877828)
        };
    
    Posicion alineacionSacar[] = new Posicion[]{
        new Posicion(0.0, -Constantes.LARGO_CAMPO_JUEGO/2),
        new Posicion(-10.223776223776223,-36.82126696832579),
        new Posicion(10.937062937062937,-36.82126696832579),
        new Posicion(-18.307692307692307,-18.529411764705884),
        new Posicion(18.545454545454543,-18.766968325791854),
        new Posicion(0.4755244755244755,-11.402714932126697),
        new Posicion(-29.244755244755243,-0.23755656108597287),
        new Posicion(27.58041958041958,-0.23755656108597287),
        new Posicion(-17.11888111888112,-0.23755656108597287),
        new Posicion(0.4755244755244755,-0.47511312217194573),
        new Posicion(14.97902097902098,-0.7126696832579186)
    };
    
    Posicion alineacionSacaRival[] = new Posicion[]{
   		new Posicion(0.0, -Constantes.LARGO_CAMPO_JUEGO/2),
        new Posicion(-10.223776223776223,-41.57239819004525),
        new Posicion(10.461538461538462,-41.80995475113122),
        new Posicion(-17.594405594405593,-29.457013574660635),
        new Posicion(17.832167832167833,-29.21945701357466),
        new Posicion(0.7132867132867133,-18.29185520361991),
        new Posicion(-25.678321678321677,-4.513574660633484),
        new Posicion(25.202797202797203,-5.226244343891403),
        new Posicion(-8.55944055944056,-5.463800904977376),
        new Posicion(0.7132867132867133,-10.452488687782806),
        new Posicion(9.272727272727272,-5.701357466063349)
    };
   
    Posicion alineacionEntrenamiento[]=new Posicion[]{
    		new Posicion(0.0, -Constantes.LARGO_CAMPO_JUEGO/2),
            new Posicion(-34.0,-52.5),
            new Posicion(-34.0,-52.5),
            new Posicion(-30.909090909090907,-52.5),
            new Posicion(-34.0,-52.5),
            new Posicion(-25.44055944055944,-52.5),
            new Posicion(-34.0,-52.5),
            new Posicion(-34.0,-52.5),
            new Posicion(-0.2595419847328244,-0.26119402985074625),
            new Posicion(0.2595419847328244,-0.26119402985074625),
            new Posicion(-27.34265734265734,-52.262443438914026)
    };
    
    public TacticaDetalle getDetalle() {
        return detalle;
    }
    int cambia = 0;

	private EstadoBalon estadoBalon;
	
    public Posicion[] getPosicionSaca(SituacionPartido sp) {
        return alineacionSacar;
    	//return alineacionEntrenamiento;
    }

    public Posicion[] getPosicionRecive(SituacionPartido sp) {
        return alineacionSacaRival;
    }
    
    /** DIEGO */
       
    /**
     * Ejecuta la logica del portero.
     * Es llamado en cada iteracion
     */
    public void ejecutarPortero(SituacionPartido sp) {
    	boolean puedoRematar=false;
    	

    	
    	for (int i : this.situacionPartido.puedenRematar()) {
    		if (i==0) {
    			puedoRematar=true;
    		}
    	}
    	
		// El balon esta cerca
		if (puedoRematar) {
				this.comandos.add(new ComandoGolpearBalon(
						0,
						Constantes.centroArcoSup, // Porteria contraria
						Constantes.REMATE_VELOCIDAD_MAX,
						Constantes.ANGULO_VERTICAL_MAX));
				return; // Nada mas
		}
		
		/*
		 * No ha sido posible interceptar asi que optar por defender
		 */
		Posicion posicionADefender = Constantes.centroArcoInf;
		
		double anguloConBalon = sp.balon().angulo(posicionADefender);
		
		double radius = 0;
		
		if (sp.balon().distancia(Constantes.centroArcoInf)>5) {
			radius = Math.max(0.2,
					Math.min(
							5, // Maximun radius
							Math.pow(sp.balon().distancia(Constantes.centroArcoInf),2)*0.010-3));
		}
		
		this.comandos.add(new ComandoIrA(0,
				posicionADefender.moverAngulo(
						anguloConBalon,
						-radius)));
		
		EstadoBalon proximoEstadoBalon;
		int indiceJugadorMasCercano, indiceRivalMasCercano;
		Posicion posicionJugadorMasCercano, posicionRivalMasCercano;
		
		/*
		 * Calcular una posible intercepcion
		 */
		for(int iteracion=0; iteracion < 25; iteracion++){
			proximoEstadoBalon = this.proximaPosicionBalon(iteracion);
			if (proximoEstadoBalon==null) {
				continue;
			}
			
			indiceJugadorMasCercano = this.indiceLlegariaMasRapido(proximoEstadoBalon.posicion, this.situacionPartido.misJugadores(), this.situacionPartido.detalleMisJugadores());
			
			if (indiceJugadorMasCercano!=0) {
				break;
			}
			
			indiceRivalMasCercano = this.indiceLlegariaMasRapido(proximoEstadoBalon.posicion, sp.rivales(), sp.detalleJugadoresRivales());
			posicionJugadorMasCercano = sp.misJugadores()[indiceJugadorMasCercano];
			posicionRivalMasCercano = sp.rivales()[indiceRivalMasCercano];
			
			if(		(sp.balon().distancia(Constantes.centroArcoInf)>Constantes.DISTANCIA_PENAL) &&
					this.iteracionesEnLlegar(indiceJugadorMasCercano,proximoEstadoBalon.posicion) <= this.iteracionesEnLlegarRival(indiceRivalMasCercano, proximoEstadoBalon.posicion)
						&& proximoEstadoBalon.posicion.distancia(posicionJugadorMasCercano) < (Constantes.getVelocidad(this.situacionPartido.detalleMisJugadores()[indiceJugadorMasCercano].getVelocidad()) * iteracion)
						&& (proximoEstadoBalon.altura <= Constantes.ALTO_ARCO) ) {
				Posicion destino = proximoEstadoBalon.posicion;
				if (destino.getY()<Constantes.centroArcoInf.getY()) {
					destino.setPosicion(destino.getX(), Constantes.centroArcoInf.getY());
				}
				this.comandos.add(new ComandoIrA(indiceJugadorMasCercano,proximoEstadoBalon.posicion));
			}
		}
	}
    
    /** DIEGO FIN */
    
    public List<Comando> ejecutar(SituacionPartido situacionPartido){
    	this.situacionPartido= situacionPartido; 
    	this.estadoBalon = new EstadoBalon(this.situacionPartido.balon(), this.situacionPartido.alturaBalon());
//    	System.out.println("Balon: (" + this.situacionPartido.iteracion() + ") " + this.situacionPartido.balon().toString());
    	/////Entrenamiento
//      if(this.ultimoEstadoBalon != null) 
//    	  System.out.println(this.situacionPartido.alturaBalon() + "  -  " + this.proximaPosicionBalon(3).altura);
////    	if(this.ultimoEstadoBalon != null) {
////    		for(int xx=1; xx<10; xx++) {
////    			if (xx>1)
////    				System.out.println("Futuro ("+xx+"):" + this.proximaPosicionBalon(xx).posicion.getY() + " - " + this.proximaPosicionBalon(xx).posicion.distancia(this.proximaPosicionBalon(xx-1).posicion) );
////    			else
////    				System.out.println("Futuro ("+xx+"):" + this.proximaPosicionBalon(xx).posicion.getY() + " - " + this.proximaPosicionBalon(xx).posicion.distancia(this.ultimoEstadoBalon.posicion) );
////    		}
////    		System.out.println("-----------------------");
////    	}
//    	comandos.clear();
//  	
//   	Posicion receptor = new Posicion(15,0);
//    	comandos.add(new ComandoGolpearBalon(8,Constantes.centroArcoSup,1,60));
//    	this.ultimaSituacionPartido = situacionPartido;
//        this.ultimoEstadoBalon = estadoBalon;
//        
//    	if(true)return comandos;
//    	double angulo = situacionPartido.balon().angulo(receptor);
//    	System.out.println("Angulo de tiro: " + angulo);
//    	double error = situacionPartido.detalleMisJugadores()[8].getPresicion();
//    	error = Constantes.getErrorAngular(error);
//    	System.out.println("Desviacion entre: " + ((1 * error - error / 2) * Math.PI) + " y " + ((0 * error - error / 2) * Math.PI));
//    	System.out.println("Apertura angulo:  " + (angulo+ (1 * error - error / 2) * Math.PI) + " - " + (angulo+(0 * error - error / 2) * Math.PI));
//    	if(true)
//    	return comandos;
//    	
//    	
//    	
//    	
//    	Posicion quita = new Posicion(-5,-5);
//   		if(this.estado != PASANDO && !situacionPartido.misJugadores()[9].equals(quita)){
//    		comandos.add(new ComandoIrA(9,quita));
//    		return comandos;
//    	}else if(situacionPartido.puedenRematar().length > 0) {
//    		double dx = Constantes.getVelocidadRemate(this.situacionPartido.detalleMisJugadores()[8].getRemate());
//    		System.out.println("Alcance: " + (dx/(1-Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO)));
//    		double distanciaBalonADestino = this.situacionPartido.balon().distancia(receptor);
//    		double velocidadRemateAjustada = distanciaBalonADestino / ((Math.pow(Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO, 20)-1)/(Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO-1));
//    		double factorRemate = velocidadRemateAjustada / Constantes.getVelocidadRemate(this.situacionPartido.detalleMisJugadores()[8].getRemate());
//    		comandos.add(new ComandoGolpearBalon(8, receptor,factorRemate, false));
//    		Pase pase = this.calculaMejorPuntoDePase(8, 9);
//    		
//    		if(pase != null) {
//    			this.paseEnCurso = pase;
//    			comandos.add(new ComandoGolpearBalon(8,pase.destino,pase.ajustePotencia,false));
//    			System.out.println("Pase a " + paseEnCurso.indiceReceptor + " a recibir en " + paseEnCurso.duracion + " Potencia: " + paseEnCurso.ajustePotencia);
//    			//double ddx = Math.cos(this.situacionPartido.balon().angulo(pase.destino))*dx*pase.ajustePotencia;
//				//double ddy = Math.sin(this.situacionPartido.balon().angulo(pase.destino))*dx*pase.ajustePotencia;
//				//System.out.println(this.proximaPosicionBalon(2, ddx, ddy, 0).posicion.toString());
//            	this.estado=PASANDO;
//    		}
//    		return comandos;
//    	} else if(this.estado==PASANDO) {
//    		this.ejecutarEstadoPasando();
//    	}
//    	if(true)
//    		return comandos;
//    	////Fin entrenamiento
    	comandos.clear();
    	if(situacionPartido.saco() || situacionPartido.sacaRival()) {
    		this.ultimaSituacionPartido = null;
    		this.ultimoEstadoBalon = null;
    		if (situacionPartido.saco()) {
    			//el jugador mas cercano se acerca a sacar hacia el balon
    			//TODO: esperar el maximo de iteraciones para dar tiempo al equipo a colocarse
    			comandos.add(new ComandoIrA(situacionPartido.balon().indiceMasCercano(situacionPartido.misJugadores()), situacionPartido.balon()));
    			this.ejecutarEstadoDesconocido();
    			//comandos.add(new ComandoGolpearBalon(situacionPartido.balon().indiceMasCercano(situacionPartido.misJugadores()),Constantes.centroCampoJuego,1,true));
    			return comandos;
    		}
    	}
    	    	

        
        //Comandos IrA por defecto para cada jugador. Si un jugador actua distinto proximos comandos IrA para ese jugador
    	//sobreescriben a estos.    	
        //cada jugador ubicarse segun alineacion1
        for (int i = 1; i < 11; i++) {
         	if(this.paseEnCurso == null || (this.paseEnCurso != null && i != this.paseEnCurso.indiceReceptor)) {
         		comandos.add(new ComandoIrA(i, alineacionAtaque[i]));
            }
        }        
        
        if(this.situacionPartido.puedenRematar().length > 0) {
        	this.estado=DESCONOCIDO;
        } else if (this.haRematadoElRival()) {
        	//System.out.println("Ha rematado el rival");
        	this.estado = INTERCEPTANDO;
        }
        
        /*
         * Ejecutar portero
         */
        ejecutarPortero(situacionPartido);
   
        switch(this.estado) {
        	case DESCONOCIDO:
        		this.ejecutarEstadoDesconocido();
        		break;
        	case PASANDO:
        		this.ejecutarEstadoPasando();
        		break;
        	case INTERCEPTANDO:
        		this.ejecutarEstadoInterceptando();
        		break;
        	case PASANDO_O_INTERCEPTANDO:
        		this.ejecutarEstadoPasandoOInterceptando();
        		break;
        	default:
        		this.ejecutarEstadoDesconocido();
        }

        this.ultimaSituacionPartido = situacionPartido;
        this.ultimoEstadoBalon = estadoBalon;
        return this.comandos;
    }

    private boolean haRematadoElRival() {
		for (int iteracionesParaRematar : this.situacionPartido.iteracionesParaRematarRival()) {
			if(iteracionesParaRematar == Constantes.ITERACIONES_GOLPEAR_BALON) {
				this.paseEnCurso = null; //Si estabamos en medio de un pase, ha sido interceptado
				return true;
			}
		}
		return false;
	}

	private void ejecutarEstadoPasandoOInterceptando() {
		if(this.haRematadoElRival()) {
			this.ejecutarEstadoInterceptando();
		} else {
			this.ejecutarEstadoPasando();
		}
	}

	//mapa de marcajes, relaciona los jugadores con los rivales a los que pueden marcar
	private Hashtable<Integer,List<Integer>> mapaMarcajes;
	//mapa de marcajes de rivales, relaciona los rivales con los jugadores que los pueden marcar
	private Hashtable<Integer,List<Integer>> mapaMarcajesRivales;
	//La posicion de los jugadores cambia ligeramente segun la posicion del balon en el campo. Cuanto mas cerca de la porteria, mas cerrada esta la defensa
	private Posicion[] defensaAjustada = new Posicion[11];
	
	
	/**
	 * Llamada cuando un jugador solo puede cubrir a un rival, comprueba que es el mejor jugador para cubrirlo y si es asi lo cubre
	 * @param jugador
	 * @param rivalACubrir
	 */
	private void marcajePorDefensaSuelto (Integer jugador, Integer rivalACubrir) {
		if(mapaMarcajesRivales.get(rivalACubrir).size() == 1) {//El rival solo puede ser marcado por este jugador
			this.marcarRival(jugador, rivalACubrir); //El jugador marca al unico rival que tiene en su area de marcaje
		} else { //el rival puede ser marcado tambien por otros jugadores
			boolean marcar=true;
			Iterator<Integer> it = mapaMarcajesRivales.get(rivalACubrir).iterator();
			while(it.hasNext()) {//Itera los jugadores que pueden cubrir a este rival
				Integer defensaAlternativo = (Integer)it.next();
				if(defensaAlternativo != jugador && mapaMarcajes.get(defensaAlternativo).size() == 1 && //si el defensa alternativo tambien tiene solo un rival a quien cubrir y la posicion del rival esta mas cerca de su posicion de defensa
				   this.defensaAjustada[defensaAlternativo].distancia(this.situacionPartido.rivales()[rivalACubrir]) < this.defensaAjustada[jugador].distancia(this.situacionPartido.rivales()[rivalACubrir]) 
				   ) {
					marcar = false; //defensaAlternativo es mejor para marcar a este rival. No agando el comando directamente para el defensaAlternativo porque aunque sea mejor, no tiene porque ser el mejor.
				}
			}
			if(marcar) { //Ninguno de los otros defensas que podrian cubrir a este jugador son mejores para hacerlo
				this.marcarRival(jugador, rivalACubrir); //El jugador marca al unico rival que tiene en su area de marcaje
			}
		}
	}
	
	/**
	 * Posiciona al jugador para marcar al rival, agnade el rival a la lista de rivales cubiertos, elimina el rival de la lista de marcajes, elimina el jugador de las entradas del mapa de marcajes, y si tras eliminarlo otros rivales quedan marcados por un solo jugador, llama recursivamente a este metodo para realizar los marcajes
	 * @param jugador
	 * @param rival
	 */
	private void marcarRival(Integer jugador, Integer rival) {
		int DISTANCIA_DE_MARCAJE=2;
		Posicion pRival = this.situacionPartido.rivales()[rival];
		comandos.add(new ComandoIrA(jugador, pRival.moverAngulo(pRival.angulo(Constantes.centroArcoInf), DISTANCIA_DE_MARCAJE)));
		this.mapaMarcajes.remove(jugador);
		this.mapaMarcajesRivales.remove(rival);
		Enumeration<Integer> e = mapaMarcajes.keys();
		while(e.hasMoreElements()) { //elimina el rival del resto de entradas en el mapa de marcajes de los jugadores
			Integer indiceJugador = e.nextElement();
			List<Integer> rivales = mapaMarcajes.get(indiceJugador);
			rivales.remove(rival);
			if(rivales.isEmpty()) {
				this.mapaMarcajes.remove(indiceJugador);
			}
		}
		Enumeration<Integer> e2 = mapaMarcajesRivales.keys();
		while(e2.hasMoreElements()) { //elimina el jugador del resto de entradas en el mapa de marcajes de los rivales
			Integer indiceRival = e2.nextElement();
			List<Integer> defensas = mapaMarcajesRivales.get(indiceRival);
			defensas.remove(jugador);
			if(defensas.size() == 1) { //Si tras la eliminacion del jugador la lista queda con uno solo, llama recursivamente a esta funcion
				this.marcajePorDefensaSuelto(defensas.get(0), indiceRival);
			}else if (defensas.isEmpty()) {
				this.mapaMarcajesRivales.remove(indiceRival);
			}
		}
	}
	
	private void ejecutarEstadoInterceptando() {
		int RADIO_ZONA_A_DEFENDER=15;
		
		//Generar los mapas de marcajes
		mapaMarcajes = new Hashtable<Integer, List<Integer>>();
		mapaMarcajesRivales = new Hashtable<Integer, List<Integer>>();
		for (int jugador = 1; jugador < 11; jugador++) {
			//Ajustar posiciones de defensa segun la proximidad del balon
			if(jugador < NUM_DEFENSAS && this.situacionPartido.balon().getY() < 0) {
				this.defensaAjustada[jugador]=this.alineacionDefensa[jugador].moverAngulo(this.alineacionDefensa[jugador].angulo(Constantes.centroArcoInf), (this.alineacionDefensa[jugador].distancia(Constantes.centroArcoInf) * ((-1)*this.situacionPartido.balon().getY())/(Constantes.LARGO_CAMPO/2)));
			} else {
				this.defensaAjustada[jugador]=this.alineacionDefensa[jugador];
			}
				
			if(this.paseEnCurso == null || (this.paseEnCurso != null && jugador != this.paseEnCurso.indiceReceptor)) {
				//Posicion por defecto en defensa
				comandos.add(new ComandoIrA(jugador, this.defensaAjustada[jugador]));
			}
			if(jugador < NUM_DEFENSAS) { //Los defensas y centros cubren a los rivales
				for(int rival=1; rival < 11; rival ++) {//Agnade al mapa los rivales que estan en la zona de marcaje de este defensa
					Posicion pRival = this.situacionPartido.rivales()[rival];
					if(this.defensaAjustada[jugador].distancia(pRival) < RADIO_ZONA_A_DEFENDER) { //TODO: sustituir la posicion de mi jugador por la posicion en la que deberia estar en defensa segun la posicion del balon
						if(!mapaMarcajesRivales.containsKey((Integer)rival)) {
							this.mapaMarcajesRivales.put((Integer)rival, new ArrayList<Integer>());
						}
						if(!mapaMarcajes.containsKey((Integer)jugador)) {
							this.mapaMarcajes.put((Integer)jugador, new ArrayList<Integer>());
						}
						mapaMarcajes.get((Integer)jugador).add((Integer)rival);
						mapaMarcajesRivales.get((Integer)rival).add((Integer)jugador);
					}
				}
			}
		}
		
		//Agnadir los comandos para los jugadores que solo pueden marcar a un rival
		Enumeration<Integer> e = mapaMarcajes.keys();
		while(e.hasMoreElements()) {
			Integer jugador = e.nextElement();
			List<Integer> rivalesACubrir = mapaMarcajes.get(jugador);
			if(rivalesACubrir.size() == 1) {
				this.marcajePorDefensaSuelto(jugador, rivalesACubrir.get(0));
			}
		}
		
		//Una vez asignados todos los jugadores que solo pueden cubrir a un rival, pueden quedar rivales por cubrir y puede que un jugador
		//tenga a mas de un rival en su area. En este caso el jugador marca al jugador mas peligroso (mas cercano a la porteria)
		e = mapaMarcajes.keys();
		while(e.hasMoreElements()) {
			Integer jugador = e.nextElement();
			List<Integer> rivalesACubrir = mapaMarcajes.get(jugador);
			Posicion posicionMasPeligrosa = null;
			Integer indiceMasPeligroso = null;
			Iterator<Integer> it = rivalesACubrir.iterator();
			while(it.hasNext()) {
				Integer rivalACubrir = it.next();
				if (indiceMasPeligroso == null) {
					indiceMasPeligroso = rivalACubrir;
					posicionMasPeligrosa = this.situacionPartido.rivales()[rivalACubrir];
				}
				else if( this.situacionPartido.rivales()[rivalACubrir].distancia(Constantes.centroArcoInf) < posicionMasPeligrosa.distancia(Constantes.centroArcoInf)) {
					indiceMasPeligroso = rivalACubrir;
					posicionMasPeligrosa = this.situacionPartido.rivales()[rivalACubrir];
				}		
			}
			if(indiceMasPeligroso != null) {
				this.marcarRival(jugador, indiceMasPeligroso);
			} else {
				System.err.println("indiceMasPeligroso nulo, esto no deberia pasar");
			}
		}
		
		EstadoBalon proximoEstadoBalon;
		int indiceJugadorMasCercano, indiceRivalMasCercano;
		Posicion posicionJugadorMasCercano;
		for(int iteracion=0; iteracion < 35; iteracion++){
			proximoEstadoBalon = this.proximaPosicionBalon(iteracion);
			if (proximoEstadoBalon!=null) {
				indiceJugadorMasCercano = this.indiceLlegariaMasRapido(proximoEstadoBalon.posicion, this.situacionPartido.misJugadores(), this.situacionPartido.detalleMisJugadores());
				indiceRivalMasCercano = this.indiceLlegariaMasRapido(proximoEstadoBalon.posicion, this.situacionPartido.rivales(), this.situacionPartido.detalleJugadoresRivales());
				posicionJugadorMasCercano = this.situacionPartido.misJugadores()[indiceJugadorMasCercano];
				
				if(this.iteracionesEnLlegar(indiceJugadorMasCercano,proximoEstadoBalon.posicion) <= this.iteracionesEnLlegarRival(indiceRivalMasCercano, proximoEstadoBalon.posicion)
						&& proximoEstadoBalon.posicion.distancia(posicionJugadorMasCercano) < (Constantes.getVelocidad(this.situacionPartido.detalleMisJugadores()[indiceJugadorMasCercano].getVelocidad()) * iteracion)
						&& (proximoEstadoBalon.altura <= Constantes.ALTURA_CONTROL_BALON || (this.situacionPartido.detalleMisJugadores()[indiceJugadorMasCercano].esPortero() && proximoEstadoBalon.altura <= Constantes.ALTO_ARCO)) ) {
					this.comandos.add(new ComandoIrA(indiceJugadorMasCercano,proximoEstadoBalon.posicion));
					break;
				}
			}
		}    	
	}

	/**
	 * Calcula el numero de iteraciones que tardaria el jugador en llegar a la posicion. 
	 * 
	 * OJO! Para los rivales se usa iteracionesEnLlegarRival
	 * @param indiceJugador - indice del jugador para el que se quiere calcular el tiempo
	 * @param destino - Posicion de la que se quiere saber cuanto se tardaria en llegar
	 * @return numero de iteraciones en llegar a la posicion
	 */
	private int iteracionesEnLlegar(int indiceJugador, Posicion destino) {
		return this.iteracionesDeOrigenADestino(this.situacionPartido.misJugadores()[indiceJugador], destino, this.situacionPartido.detalleMisJugadores()[indiceJugador].getVelocidad());
	}

	/**
	 * Calcula el numero de iteraciones que tardaria el rival en llegar a la posicion.
	 * @param indiceRival - indice del rival para el que se quiere calcular el tiempo
	 * @param destino - Posicion de la que se quiere saber cuanto se tardaria en llegar
	 * @return numero de iteraciones en llegar a la posicion
	 */
	private int iteracionesEnLlegarRival(int indiceRival, Posicion destino) {
		return this.iteracionesDeOrigenADestino(this.situacionPartido.rivales()[indiceRival], destino, this.situacionPartido.detalleJugadoresRivales()[indiceRival].getVelocidad());
	}
	
	/**
	 * Calcula cuanto tardaria en llegar un jugador de una posicion a otra
	 * 
	 * @param origen Posicion del jugador
	 * @param destino Posicion donde quiere llegar
	 * @param velocidad Velocidad del jugador
	 * @return Numero de iteraciones que el jugador tardaria en llegar al la posicion de destino
	 */
	private int iteracionesDeOrigenADestino(Posicion origen, Posicion destino, double velocidad) {
		return (int)Math.ceil((origen.distancia(destino)-Constantes.DISTANCIA_CONTROL_BALON)/Constantes.getVelocidad(velocidad));
	}
	/**
	 * Obtiene el indice del jugador que llegaria mas rapido a esta posicion teniendo en cuenta su velocidad y la distancia a la posicion
	 * @param destino
	 * @param posiciones
	 * @param detalles
	 * @return indice del jugador que llegaria mas rapido
	 * */
    private int indiceLlegariaMasRapido(Posicion destino, Posicion[] posiciones, JugadorDetalle[] detalles) {
        double max = Integer.MAX_VALUE;
        int tiempo;
        int idx = -1;
        for (int i = 0; i < posiciones.length; i++) {
            tiempo = this.iteracionesDeOrigenADestino(posiciones[i], destino, detalles[i].getVelocidad());
            if (tiempo < max) {
                max = tiempo;
                idx = i;
            }

        }
        return idx;
    }

	private void ejecutarEstadoPasando() {
		EstadoBalon proximoEstadoBalon = this.proximaPosicionBalon(this.paseEnCurso.duracion);
    	if(this.ultimaSituacionPartido != null && proximoEstadoBalon != null) {
    		//System.out.println("Receptor corriendo hacia prediccion en " +this.paseEnCurso.duracion + ". Llegara en: " + this.iteracionesEnLlegar(this.paseEnCurso.indiceReceptor, proximoEstadoBalon.posicion));
    		this.comandos.add(new ComandoIrA(this.paseEnCurso.indiceReceptor,proximoEstadoBalon.posicion));
    	} else {
    		//System.out.println("Receptor corriendo hacia destino pase: " + this.paseEnCurso.destino.toString());
    		this.comandos.add(new ComandoIrA(this.paseEnCurso.indiceReceptor,this.paseEnCurso.destino));
    	}
    	this.paseEnCurso.duracion--;
    	if(this.paseEnCurso.duracion <= 0) {
    		this.estado=DESCONOCIDO;
    	}
	}

	private void ejecutarEstadoDesconocido() {
		
		int jugadorMasCercanoAlBalon = this.situacionPartido.balon().indiceMasCercano(this.situacionPartido.misJugadores());
        
		if(this.proximaPosicionBalon() != null) {
        	this.comandos.add(new ComandoIrA(jugadorMasCercanoAlBalon, this.proximaPosicionBalon().posicion));
        } else {
        	this.comandos.add(new ComandoIrA(jugadorMasCercanoAlBalon, this.situacionPartido.balon()));
        }
		
		if(this.situacionPartido.puedenRematar().length>0) {
			//recorre los jugadores que pueden rematar
	        for (int i : this.situacionPartido.puedenRematar()) {
	        	//System.out.println(this.situacionPartido.detalleMisJugadores()[i].getNumero() + " tiene el balon");
	            this.paseEnCurso = this.calculaMejorPuntoDePase(i);
	        	if(this.paseEnCurso != null){// && mejorPuntoDePase.getY() > this.situacionPartido.misJugadores()[i].getY()) {
	            	//Ajustar la potencia para que llegue al destino del pase en el numero de iteraciones calculado
	            	comandos.add(new ComandoGolpearBalon(i,this.paseEnCurso.destino, this.paseEnCurso.ajustePotencia,false));
	            	comandos.add(new ComandoIrA(this.paseEnCurso.indiceReceptor,this.paseEnCurso.destino));
	    			this.estado = PASANDO;
	            } else {
	            	//System.out.println("No deberia llegar aqui. Toquecito adelante de " + this.situacionPartido.detalleMisJugadores()[i].getNumero());
	            	comandos.add(new ComandoGolpearBalon(i,90,0.3,false));
	            }
	            
	            //si el jugador que remata esta a menos de 20 metros del arco rival -> remata al arco
	        	//salvo si el pase promete mas
	            if (this.situacionPartido.misJugadores()[i].distancia(Constantes.centroArcoSup) < 20) {
	            	double error = Constantes.getErrorAngular(situacionPartido.detalleMisJugadores()[i].getPresicion());
	            	error = (error/2)*Math.PI;
	            	if(this.situacionPartido.balon().distancia(Constantes.posteIzqArcoSup) < this.situacionPartido.balon().distancia(Constantes.posteDerArcoSup)) {
	            		comandos.add(new ComandoGolpearBalon(i, (this.situacionPartido.balon().angulo(Constantes.posteIzqArcoSup.moverAngulo(0, Constantes.RADIO_BALON))/(Math.PI / 180d))-(error/(Math.PI / 180d)),1,false));
	            	} else {
	            		comandos.add(new ComandoGolpearBalon(i, (this.situacionPartido.balon().angulo(Constantes.posteDerArcoSup.moverAngulo(Math.PI, Constantes.RADIO_BALON))/(Math.PI / 180d))+error/(Math.PI / 180d),1,false));
	            	}
	            }                
	        }
	        if(this.situacionPartido.puedenRematarRival().length>0) {
	        	//Los dos equipos pueden rematar en esta iteracion, se elije al azar quien golpea el balon
	        	//Dependiendo de quien golpee el proximo estado seria PASANDO o INTERCEPTANDO
	        	//Para elegir usamos iteracionesParaRematar e iteracionesParaRematarRival en la siguiente iteracion
	        	this.estado = PASANDO_O_INTERCEPTANDO;
	        }
		} else if(this.situacionPartido.puedenRematarRival().length>0) {//Solo el rival puede rematar
			this.estado = INTERCEPTANDO;//No es seguro que remate, pero muy probable
		} else { //Nadie puede rematar en esta iteracion
			//Normalmente no deberia llegarse a este punto. Como salvaguarda, el jugador mas cercano a la pelota corre hacia ella.
			comandos.add(new ComandoIrA(this.indiceLlegariaMasRapido(this.situacionPartido.balon(), this.situacionPartido.misJugadores(), this.situacionPartido.detalleMisJugadores()),this.situacionPartido.balon()));
		}
	}

	
	/**
	 * @param indiceJugador Jugador que efectua el pase
	 * @return
	 */
	private Pase calculaMejorPuntoDePase(int indiceJugador) {
		Posicion mejorPuntoDePase = new Posicion(Constantes.cornerInfIzq);
        Posicion candidatoAMejorPuntoDePase = new Posicion(Constantes.cornerInfIzq);
        Pase mejorPase=null;
        Pase candidatoAMejorPase = null;
        
        for (int indiceReceptorPase=1; indiceReceptorPase<11; indiceReceptorPase++) {//TODO: Excluir el portero
        		candidatoAMejorPase = calculaMejorPuntoDePase(indiceJugador, indiceReceptorPase);
        		if(candidatoAMejorPase!=null) {
        			candidatoAMejorPuntoDePase = candidatoAMejorPase.destino;
        		}
        		if(candidatoAMejorPuntoDePase != null && candidatoAMejorPuntoDePase.getY() > mejorPuntoDePase.getY()) {
        			mejorPuntoDePase = candidatoAMejorPuntoDePase;
        			mejorPase = candidatoAMejorPase;
        		}
        }
        return mejorPase;
	}
		
	/**
	 * @param indiceJugador Jugador que efectua el pase
	 * @param indiceReceptor Jugador que recibe el pase
	 * @return
	 */
	private Pase calculaMejorPuntoDePase(int indiceJugador, int indiceReceptor) {
		final int NUM_DIVISIONES=16;
		final int MAX_ITERACIONES_PASE=20;
		
		int margenSeguridad=5; //Numero de iteraciones de diferencia entre lo que tardaria en llegar un jugador nuestro y un contrario al pase
							   //TODO: Variar el margen segun sea o no posible hacer pases. Posiblemente pasar a un atributo de la clase
		Pase pase = null;
		Posicion posicionReceptor = this.situacionPartido.misJugadores()[indiceReceptor];
    	double potenciaJugador = this.situacionPartido.detalleMisJugadores()[indiceJugador].getRemate();
    	double velocidadReceptor = Constantes.getVelocidad(this.situacionPartido.detalleMisJugadores()[indiceReceptor].getVelocidad());
		//double alcance =0;
		double velocidadRemate = Constantes.getVelocidadRemate(potenciaJugador);
			
		//Suma de n terminos de una serie geometrica: Sn = a1 * (((r^n)-1) / (r-1))
		//Le sumo velocidadRemate por el avance de la primera iteracion, que no esta multiplicado por el factor de disminucion
		double alcance = velocidadRemate + (velocidadRemate * ((Math.pow(Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO, MAX_ITERACIONES_PASE)-1)/(Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO-1)));
		
		//Comprobar que la distancia entre el balon y el receptor es menor que el alcance del pase + lo que el jugador puede
    	//avanzar en 'iteracion' numero de iteraciones hacia el balon.
    	if(this.situacionPartido.balon().distancia(posicionReceptor) <= alcance + (velocidadReceptor*MAX_ITERACIONES_PASE) + Constantes.DISTANCIA_CONTROL_BALON) {
    		//En el treeset ordenado almaceno las posiciones en orden de mejor a peor, de forma que cuando encuentre una valida
    		//puedo dejar de evaluar las demas, ya que seran peores posiciones que esta.
    		TreeSet<Posicion> destinos = new TreeSet<Posicion>(new ComparadorPosiciones());
    		//Crear nube de puntos correspondiente a las circunferencias que el jugador podria alcanzar en cada iteracion
    		for(int j=0; j<=MAX_ITERACIONES_PASE; j++) {
    			if(this.situacionPartido.iteracionesParaRematar()[indiceReceptor] < j) {
    				double FACTOR_DIVISION_CIRCUNFERENCIA = 2*Math.PI/NUM_DIVISIONES;
    				for(int division=0; division<NUM_DIVISIONES; division++) {
    					destinos.add(posicionReceptor.moverAngulo(division*FACTOR_DIVISION_CIRCUNFERENCIA, (velocidadReceptor*j)));
    				}
    			}
    		}
   			
    			for(Posicion posicionDestino: destinos) {
    				//Comprobar que no se sale del campo
					if(Math.abs(posicionDestino.getY()) < (Constantes.LARGO_CAMPO_JUEGO/2) &&
					   Math.abs(posicionDestino.getX()) < (Constantes.ANCHO_CAMPO_JUEGO/2)) {
						//Comprobar que el pase tiene potencia suficiente
						if(alcance >= this.situacionPartido.balon().distancia(posicionDestino)) {
							int iteracionesEnLlegar = this.iteracionesEnLlegar(indiceReceptor, posicionDestino);
							double error = Constantes.getErrorAngular(situacionPartido.detalleMisJugadores()[indiceJugador].getPresicion());
					    	error = (error/2)*Math.PI;
					    	//Suma de n terminos de una serie geometrica: Sn = a1 * (((r^n)-1) / (r-1)). Despejo a1 para calcular la potencia ajustada de tiro
				    		//Le sumo velocidadRemate por el avance de la primera iteracion, que no esta multiplicado por el factor de disminucion
					    	double distanciaBalonADestino = this.situacionPartido.balon().distancia(posicionDestino);
				    		double velocidadRemateAjustada = distanciaBalonADestino / ((Math.pow(Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO, iteracionesEnLlegar)-1)/(Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO-1));
				    		
				    		boolean interceptado=false;
//					    	//Comprobar que no se puede interceptar
//							double v = Constantes.getVelocidadRemate(this.situacionPartido.detalleMisJugadores()[indiceJugador].getRemate());
				    		double dx = Math.cos(this.situacionPartido.balon().angulo(posicionDestino)) * velocidadRemateAjustada;
				    		double dy = Math.sin(this.situacionPartido.balon().angulo(posicionDestino)) * velocidadRemateAjustada;
				    		for (int k=1; k<=iteracionesEnLlegar;k++) { 
								EstadoBalon balonEnCamino = this.proximaPosicionBalon(k, dx, dy, 0);
								int rivalMasCercano = this.indiceLlegariaMasRapido(balonEnCamino.posicion, this.situacionPartido.rivales(), this.situacionPartido.detalleJugadoresRivales());
								
								if(balonEnCamino!= null && (this.iteracionesEnLlegarRival(rivalMasCercano, balonEnCamino.posicion) < (k+margenSeguridad))) {
									interceptado=true;
						   			break;
								}
					    	}
				    		if(!interceptado) {
				    			double factorRemate = velocidadRemateAjustada / Constantes.getVelocidadRemate(this.situacionPartido.detalleMisJugadores()[indiceJugador].getRemate());
				    			pase = new Pase(indiceReceptor, posicionDestino, iteracionesEnLlegar, factorRemate);
				    			return pase;
				    		} else {
				    			//System.out.println(this.situacionPartido.detalleMisJugadores()[indiceReceptor].getNumero() + " recibiria el pase en " + posicionDestino + " pero seria interceptado.");	
				    		}
				    		//System.out.println(this.situacionPartido.detalleMisJugadores()[pase.indiceReceptor].getNumero() + " recibira el pase en " + pase.destino + " en " + pase.duracion + " iteraciones. Ajuste de potencia: " + pase.ajustePotencia);
				    	}
					}
    			}//for
		}
    	return null;
	}


	/**
     * Llama a la function proximaPosicionBalon para la siguiente iteracion
     * @return estado del balon en la proxima iteracion o null si no hay al menos un estado previo con el que hacer los calculos
     */
    private EstadoBalon proximaPosicionBalon() {
    	return(this.proximaPosicionBalon(1));
    }
    
    /**
     * Trata de predecir la posicion del balon en el futuro. Devuelve el estado dentro del numero de iteraciones indicado.
     * La prediccion de altura solo es correcta hasta que el balon da el primer bote. Suponemos que nunca vamos a necesitar la
     * prediccion de altura despues del bote.
     * @param iteracion Iteracion en el futuro para la que se quiere conocer la posicion del balon. 1 indica la siguiente iteracion
     * @return estado del balon en el futuro o null si no hay al menos un estado previo con el que hacer los calculos
     */
    private EstadoBalon proximaPosicionBalon(int iteracion) {
		if(this.ultimoEstadoBalon != null) {
			double dx = this.estadoBalon.posicion.getX() - this.ultimoEstadoBalon.posicion.getX();
			double dy = this.estadoBalon.posicion.getY() - this.ultimoEstadoBalon.posicion.getY();
			double dz = estadoBalon.altura - this.ultimoEstadoBalon.altura;
			
			double balonDx;
			double balonDy;
			double balonDz=0.0;
			
			double f;
			
			if (this.estadoBalon.altura < Constantes.G * 3) { //balon por el suelo
				balonDz = this.estadoBalon.altura;
				f=Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
			} else { //balon por el aire
				//for(int i=1; i<=iteracion; i++) {
					//dz = Math.round((dz - Constantes.G)/Constantes.G) * Constantes.G;
				//	dz = (dz - Constantes.G);
				//	balonDz += dz;
				//}
				f=Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
			}
			//Suma de serie geometrica de factor dx*f y razon f
			balonDx = (dx*f)*(Math.pow(f, iteracion)-1)/(f-1);
			balonDy = (dy*f)*(Math.pow(f, iteracion)-1)/(f-1);
			
			//(dz-G) + (dz-2G) + (dz-3G) + ... + (dz-nG) = S(dz-iG) = n*dz - G*S(i) = n*dz -G*(n(1+n)/2)
			//suma de serie aritmetica de factor 1: S(i)=1+2+3+4+...+n = n(a1+an)/2 -----------^^^^^^^^^
			balonDz = iteracion*dz-(Constantes.G*((iteracion*(1+iteracion))/2));
			return new EstadoBalon(new Posicion(estadoBalon.posicion.getX() + balonDx,
												estadoBalon.posicion.getY() + balonDy),
								   Math.max(0, estadoBalon.altura + balonDz));
		} else {//Sin la posicion anterior no se puede calcular la siguiente
			
			return null;
		}
    }
    
    /**
     * Devuelve el estado dentro del numero de iteraciones indicado basado en el vector velocidad con el que se golpea el balon
     * La prediccion de altura solo es correcta hasta que el balon da el primer bote. Suponemos que nunca vamos a necesitar la
     * prediccion de altura despues del bote.
     * @param iteracion Iteracion en el futuro para la que se quiere conocer la posicion del balon. 1 indica la siguiente iteracion
     * @param dx Componente x del vector velocidad
     * @param dy Componente y del vector velocidad
     * @param dy Componente z del vector velocidad
     * @return estado del balon en el futuro o null si no hay al menos un estado previo con el que hacer los calculos
     */
    private EstadoBalon proximaPosicionBalon(int iteracion, double dx, double dy, double dz) {
    	
        /* La implementacion en la clases Partido correspondiente al calculo de posiciones cuando se golpea el balon
         * no aplica los factores de disminucion de velocidad, lo que quiere decir que este metodo debe no aplicarlos
         * para la primera iteracion pero si para las siguientes. De ahi la diferencia en implementacion con el metodo
         * proximaPosicionBalon(int iteracion), que es uniforme.
         */
    	double balonDx;
		double balonDy;
		double balonDz=0.0;
		
		double f;
		
		if (this.estadoBalon.altura < Constantes.G * 3) { //balon por el suelo
			balonDz = this.estadoBalon.altura;
			f=Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
		} else { //balon por el aire	        	
			for(int i=1; i<=iteracion; i++) {
				dz = Math.round((dz - Constantes.G)/Constantes.G) * Constantes.G;
				balonDz += dz;
			}
			f=Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
		}
		//Suma de serie geometrica de factor dx y razon f
		balonDx = (dx)*(Math.pow(f, iteracion)-1)/(f-1);
		balonDy = (dy)*(Math.pow(f, iteracion)-1)/(f-1);
	
		return new EstadoBalon(new Posicion(estadoBalon.posicion.getX() + balonDx,
											estadoBalon.posicion.getY() + balonDy),
							   estadoBalon.altura + balonDz);
	}
    
}